import unicodedata
from collections import defaultdict
from io import StringIO

from speech.commands import LangChangeCommand
from .blocks import BLOCKS, BLOCK_RSHIFT

LATIN = [
	u"en", u"ha", u"so", u"id", u"la", u"sw", u"eu",
	u"nr", u"zu", u"xh", u"ss", u"st", u"tn", u"ts",
	u"cs", u"af", u"pl", u"hr", u"ro", u"sk", u"sl", u"tr", u"hu", u"az",
	u"et", u"sq", u"ca", u"es", u"gl", u"fr", u"de", u"nl", u"it", u"da", u"is", u"nb", u"sv",
	u"fi", u"lv", u"pt", u"ve", u"lt", u"tl", u"cy", u"vi", "no"
]

CYRILLIC = ["ru", "uk", "kk", "uz", "mn", "sr", "mk", "bg", "ky"]
ARABIC = ["ar", "fa", "ps", "ur"]
CJK = ["zh", "ja", "ko"]

SINGLETONS = {
	"Armenian" : "hy",
	"Hebrew" : "he",
	"Bengali" : "bn",
	"Gurmukhi": "pa",
	"Greek" : "el",
	"Gujarati" : "gu",
	"Oriya" : "or",
	"Tamil" : "ta",
	"Telugu" : "te",
	"Kannada" : "kn",
	"Malayalam" : "ml",
	"Sinhala" : "si",
	"Thai" : "th",
	"Lao" : "lo",
	"Tibetan" : "bo",
	"Burmese" : "my",
	"Georgian" : "ka",
	"Mongolian" : "mn",
	"Khmer" : "km",
}

# Config keys to get languages to revert to, when in dobt
_configKeys = {
	"Cyrillic": "cyrillic",
	"CJK Unified Ideographs": "CJK",
}

for charset in ("Basic Latin", "Extended Latin", "Latin Extended-B"):
	_configKeys[charset] = "latin"
for charset in ("Arabic", "Arabic Presentation Forms-A", "Arabic Presentation Forms-B"):
	_configKeys[charset] = "arabic"

class LanguageDetector(object):

	def __init__(self, availableLanguages, scriptSettings):
		self.scriptSettings = scriptSettings
		# We only work with language codes yet, no dialects.
		availableLanguages = frozenset(l.split("_")[0] for l in availableLanguages)
		# Cache what are the unicode blocks supported by each language.
		# Only cache for languages we have available
		languageBlocks = defaultdict(lambda: [])
		# Basic latin and extended latin are considered the same.
		for l in (set(LATIN) & availableLanguages):
			languageBlocks[l].extend([u"Basic Latin", u"Extended Latin"])
		# For Cyrillic
		for l in (set(CYRILLIC) & availableLanguages):
			languageBlocks[l].append(u"Cyrillic")
		# For Arabic
		for l in (set(ARABIC) & availableLanguages):
			languageBlocks[l].extend([u"Arabic", u"Arabic Presentation Forms-A", u"Arabic Presentation Forms-B"])
		# If we have korian, store its blocks.
		if u"ko" in availableLanguages:
			for block in [u"Hangul Syllables", u"Hangul Jamo", u"Hangul Compatibility Jamo", u"Hangul"]:
				languageBlocks[u"ko"].append(block)
			# Same for greek.
		if u"el" in availableLanguages:
			languageBlocks[u"el"].append(u"Greek and Coptic")
		# And japonese.
		if u"ja" in availableLanguages:
			languageBlocks[u"ja"].extend([u"Kana", u"CJK Unified Ideographs"])
		# Chinese (I have some dobts here).
		if u"zh" in availableLanguages:
			languageBlocks[u"zh"].extend([u"CJK Unified Ideographs", u"Bopomofo", u"Bopomofo Extended", u"KangXi Radicals"])
		# Ad singletone languages (te only language for the range)
		for k, v in SINGLETONS.items():
			if v in availableLanguages:
				languageBlocks[v].append(k)
		self.languageBlocks = languageBlocks

		# cache a reversed version of the hash table too.
		blockLanguages = defaultdict(lambda: [])
		for k, v in languageBlocks.items():
			for i in v:
				blockLanguages[i].append(k)
		self.blockLanguages = blockLanguages

	def add_detected_language_commands(self, speechSequence, defaultLang, ignoreNumbersInLanguageDetection, ignorePunctuationInLanguageDetection):
		sb = StringIO()
		charset = None
		curLang = defaultLang
		tmpLang = curLang.split("_")[0]
		for command in speechSequence:
			if isinstance(command, LangChangeCommand):
				if command.lang is None:
					curLang = defaultLang
				else:
					curLang = command.lang
				tmpLang = curLang.split("_")[0]
				yield command
				charset = None # Whatever will come, reset the charset.
			elif isinstance(command, str):
				sb = StringIO()
				#command = str(command)
				prevInIgnore = False
				for c in command:
					# For non-alphanumeric characters, revert to  the currently set language if in the ASCII range
					block = ord(c) >> BLOCK_RSHIFT
					if c.isspace():
						sb.write(c)
						continue
					if unicodedata.category(c)[0] in "NP":
						if ignoreNumbersInLanguageDetection and c.isdigit():
							sb.write(c)
							continue
						if ignorePunctuationInLanguageDetection and not c.isdigit():
							sb.write(c)
							continue
						if prevInIgnore:
							# Digits and ascii punctuation. We already calculated
							sb.write(c)
							continue
						prevInIgnore = True
						charset = None # Revert to default charset, we don't care here and  have to recheck later
						if tmpLang != curLang.split("_")[0]:
							if sb.getvalue():
								yield sb.getvalue()
								sb = StringIO()
							yield LangChangeCommand(curLang)
							tmpLang = curLang.split("_")[0]
						sb.write(c)
						continue

						# Process alphanumeric characters.
					prevInIgnore = False
					newCharset = BLOCKS[block]
					if newCharset == charset:
						sb.write(c)
						continue
					charset = newCharset
					if charset in self.languageBlocks[tmpLang]:
						sb.write(c)
						continue
					# Find the new language to use
					newLang = self.find_language_for_charset(charset, curLang)
					newLangFirst = newLang.split("_")[0]
					if newLangFirst == tmpLang:
						# Same old...
						sb.write(c)
						continue
					# Change language
					# First yield the string we already have.
					if sb.getvalue():
						yield sb.getvalue()
						sb = StringIO()
					tmpLang = newLangFirst
					if newLang == curLang:
						yield LangChangeCommand(newLang)
					else:
						yield LangChangeCommand(tmpLang)
					sb.write(c)
				# Send the string, if we have one:
				if sb.getvalue():
					yield sb.getvalue()
			else:
				yield command

	def find_language_for_charset(self, charset, curLang):
		langs = self.blockLanguages[charset]
		if not langs or curLang.split("_")[0] in langs:
			return curLang
		# See if we have any configured language for this charset.
		if charset in _configKeys:
			configKey = _configKeys[charset]
			if hasattr(self.scriptSettings, configKey):
				return getattr(self.scriptSettings, configKey)
		return langs[0]
